/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: propetry_service.cpp
 *
 * Purpose: property service implementation
 *
 * Developer:
 *   wen.gu , 2019-10-16
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "property_service.h"
#include "panda/sys/internal/system_property_impl.h"
#include "panda/core/vfs.h"
#include "panda/core/directory.h"
#include "panda/core/utils.h"
#include "panda/sys/serializer.h"
#include "panda/sys/deserializer.h"
#include "panda/core/json.h"
 
#define LOG_TAG "prps"
#include "panda/core/log.h"

namespace panda
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define PERSIS_PROP_FILE_NAME "_my_PersistProp_"
#define PROP_SRV_NAME "property_service"
#define PROP_SRV_SIG_NAME "property_changed"
#define PROP_SRV_METHOD_SET "set"
#define PROP_SRV_METHOD_GET "get"

#if defined(_WIN32)
#define DEFAULT_PROP_FILE_PATH "\\etc\\prop\\default.prop" 
#else
#define DEFAULT_PROP_FILE_PATH "/etc/prop/default.prop" 
#endif
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/


using namespace panda::sys::internal_impl;
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

PropertyService::PropertyService()
{
    /** todo something */
}

PropertyService::~PropertyService()
{
    /** todo something */
    if (mSrv)
    {
        mSrv->stop();
        mSrv = nullptr;
    }
}


PropertyService::PandaErrc PropertyService::initialized(const std::string& defPropFile)
{
    /** owner: true, only current service can do property set */
    if (SystemProperty::instance().reinit(true))
    {
        if (parsePropertyFile(defPropFile))
        {
            if (parsePersistProp())
            {
                /** initialize persistency property */
                return PandaErrc::OK;
            }

        }

        return PandaErrc::InsufficientResources;
    }
    else
    {
        LOGE("reinitialize system property failed\n");
    }



    return PandaErrc::NoInit;
}

PropertyService::PandaErrc PropertyService::start()
{
    mSrv = std::make_shared<IpcServiceProvider>(PROP_SRV_NAME);



    mSrv->registerMethod(PROP_SRV_METHOD_SET,
        [this](panda::sys::IpcServiceRequestHandlePtr reqHdl, const std::string& method, const panda::sys::IpcPacketData& pkt)
        {
            return set(reqHdl, method, pkt);
        });

    mSrv->registerMethod(PROP_SRV_METHOD_GET,
        [=](panda::sys::IpcServiceRequestHandlePtr reqHdl, const std::string& method, const panda::sys::IpcPacketData& pkt)
        {
            return get(reqHdl, method, pkt);
        });

    return mSrv->start();
}

PropertyService::PandaErrc PropertyService::stop()
{
    if (mSrv)
    {
        return mSrv->stop();
    }

    return PandaErrc::InvalidStatus;
}

/** get property from service
    * reqHdlr [in] response handle
    * method  [in] method name
    * param   [in] the parameter for request
    *              [mandatory ] name:  the name of property key
    *
    * reply   [out]: [mandatory ] code : error code
    *                [mandatory ] descroption: the descriptor for error code
    *                [optional]   prop: the value of property.
    *
    */
PropertyService::PandaErrc PropertyService::get(IpcServiceRequestHandlePtr reqHdl, const std::string& method, const IpcPacketData& param)
{
    PandaErrc ret = PandaErrc::BadParameter;      
    panda::sys::Serializer ser;
    
    if (param.payload)
    {
        std::string keyName((const char*)param.payload.get());
        std::string prop;
        if (_SystemPropertyGet(keyName, prop))
        {
            ser.write(prop);
            ret = PandaErrc::OK;
        }
        else
        {
            ret = PandaErrc::NotFound;
        }
    }

    ser.write((uint8_t)ret);
    
    mSrv->sendResponse(reqHdl, ser.payload(), ser.size());

    return ret;
}

/** set property to service
    * reqHdlr [in] response handle
    * method  [in] method name
    * param   [in] the parameter for request
    *              [mandatory ] name:  the name of property key
    *              [mandatory ] prop:  the value of property

    * reply   [out]: [mandatory ] code : error code
    *                [mandatory ] descroption: the descriptor for error code
    *
    */
PropertyService::PandaErrc PropertyService::set(IpcServiceRequestHandlePtr reqHdl, const std::string& method, const IpcPacketData& param)
{
    panda::core::AutoLock al(mLock);
    panda::sys::Serializer ser;
    PandaErrc ret = PandaErrc::BadParameter;
    if (param.payload && (param.size > 2)) /** '\0' 1byte and  val size 1byte */
    {
        panda::sys::Deserializer deser(param.payload, param.size);

        std::string keyName; 
        std::string prop;
        deser.read(keyName);         
        deser.read(prop);

        if (checkPropKey(keyName))
        {
            if (prop.size() < PROPERTY_VALUE_MAX_SIZE)
            {
                const PropNode* pn = _SystemPropertyFind(keyName);
                if (pn)
                {
                    if (startWith(keyName, "ro."))
                    {
                        LOGE("Read-only property was already set\n");
                        ret = PandaErrc::OperationNotPermitted;
                    }
                    else
                    {
                        ret = _SystemPropertyUpdate((PropNode*)pn, prop) ? PandaErrc::OK : PandaErrc::Undefined;
                    }
                }
                else
                {
                    if (_SystemPropertySet(keyName, prop))
                    {
                        ret = PandaErrc::OK;
                    }
                    else
                    {
                        LOGE("add property: %s failed\n", keyName.c_str());
                        ret = PandaErrc::Undefined;
                    }                    
                }

                if (PandaErrc::OK == ret)
                {
                    if (startWith(keyName, "persis."))
                    {
                        ret = writePersistentProperty(keyName, prop);
                    }
                }
            }
            else
            {
                LOGE("property value length(%d) out of range(0 - %d)\n",
                    prop.size(), (PROPERTY_VALUE_MAX_SIZE - 1));
            }
        }
    }

    ser.write((uint8_t)ret);
    mSrv->sendResponse(reqHdl, ser.payload(), ser.size());

    return ret;
}

bool PropertyService::parsePropertyFile(const std::string& propFile)
{
    panda::core::VFS fs;
    PandaErrc ret = fs.fileOpen(propFile, panda::core::VFS::OpenMode::R);

    if (ret == PandaErrc::OK)
    {
        int64_t fSize = fs.fileSize();
        if (fSize > 0)
        {
            char* buf = new char[(uint32_t)fSize + 1];
            int32_t rSize = fs.fileRead(0, buf, (uint32_t)fSize);

            if (rSize == fSize)
            {
                buf[rSize] = '\0';
                panda::core::Value props;

                if (panda::core::JsonParse(buf, props))
                {
                    panda::per::KeyArray ka = props.getMembers();

                    for (size_t i = 0; i < ka.size(); i++)
                    {
                        std::string val = props[ka[i]].asString();

                        PandaErrc ret = SystemProperty::instance().add(ka[i], val);

                        if (ret != PandaErrc::OK)
                        {
                            /** todo, is need break the initailize flow, refine me?? */
                        }                        
                    }

                    return true;
                }
                else
                {
                    LOGE("parse property file data failed\n");
                }
            }
            else
            {
                LOGE("read property from default prop file failed(req:%lld, ret:%d)\n",
                    fSize, rSize);
            }
        }

        fs.fileClose();
    }

    return false;
}

bool PropertyService::parsePersistProp()
{
    mKVS = KeyValueStorage::OpenKeyValueStorage(PERSIS_PROP_FILE_NAME);

    if (mKVS)
    {
        panda::per::KeyArray ka = mKVS->getAllKeys();
        for (size_t i = 0; i < ka.size(); i++)
        {
            std::string val;
            if (mKVS->getValue(ka[i], val))
            {
               PandaErrc ret = panda::sys::internal_impl::SystemProperty::instance().add(ka[i], val);

               if (ret != PandaErrc::OK)
               {
                   /** todo, is need break the initailize flow, refine me?? */
               }
            }
        }

        return true;
    }

    return false;
}

/** valid: true, invalid: false */
bool PropertyService::checkPropKey(const std::string& key)
{
    size_t namelen = key.size();

    if (namelen < 1)
    {
        return false;
    }

    if (key[0] == '.')
    {
        return false;
    }
        
    if (key[namelen - 1] == '.')
    {
        return false;
    }

    /* Only allow alphanumeric, plus '.', '-', '@', ':', or '_' */
    /* Don't allow ".." to appear in a property name */
    for (size_t i = 0; i < namelen; i++) 
    {
        if (key[i] == '.') 
        {
            // i=0 is guaranteed to never have a dot. See above.
            if (key[i - 1] == '.')
            {
                return false;
            }
            continue;
        }    
        if (key[i] == '_' || key[i] == '-' || key[i] == '@' || key[i] == ':')
        {
            continue;
        }

        if (key[i] >= 'a' && key[i] <= 'z')
        {
            continue;
        }
        if (key[i] >= 'A' && key[i] <= 'Z')
        {
            continue;
        }

        if (key[i] >= '0' && key[i] <= '9')
        {
            continue;
        }

        return false;
    }

    return true;
}

inline bool PropertyService::startWith(const std::string& str, const std::string& prefix)
{
    return memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0;
}

PropertyService::PandaErrc PropertyService::writePersistentProperty(const std::string& key, const std::string& val)
{
    if (mKVS->setValue(key, val))
    {
        if (mKVS->syncToStorage())
        {
            return PandaErrc::OK;
        }
        else
        {
            LOGE("sync persistent property(%s) to storage failed\n", key.c_str());
        }
    }

    return PandaErrc::Undefined;

}

} /** namespace panda */

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[])
{
    if (argc > 2)
    {
        char* pandaRootPath = argv[1];
        if (pandaRootPath)
        {
            if (panda::core::Directory::IsAccessale(pandaRootPath))
            {
                panda::PropertyService propSrv;
                std::string posRootPath = pandaRootPath;
                posRootPath += DEFAULT_PROP_FILE_PATH;

                if (propSrv.initialized(posRootPath) == panda::PropertyService::PandaErrc::OK)
                {
                    if (propSrv.start() == panda::PropertyService::PandaErrc::OK)
                    {
                        while (true)
                        {
                            panda::core::SleepSec(2); /** todo, refine me? is need catch signal?? */
                        }
                    }
                    else
                    {
                        LOGE("start property service failed\n");
                    }
                }
                else
                {
                    LOGE("initialize property service failed\n");
                }
            }
            else
            {
                LOGE("invalid panda OS root path: %s\n", pandaRootPath);
            }
        }
    }
    else
    {
        LOGE("need PandaOS root path as parameter\n");
    }

    return 0;
}
